All files / web/src/app/api/curriculum/[playerId]/recordings route.ts

0% Statements 0/105
0% Branches 0/1
0% Functions 0/1
0% Lines 0/105

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106                                                                                                                                                                                                                   
/**
 * API route for listing vision recordings for a player
 *
 * GET /api/curriculum/[playerId]/recordings
 *
 * Returns list of all vision recordings for the player, sorted by start time (newest first).
 */

export const dynamic = 'force-dynamic'

import { NextResponse } from 'next/server'
import { desc, eq } from 'drizzle-orm'
import { db } from '@/db'
import { visionRecordings } from '@/db/schema'
import { withAuth } from '@/lib/auth/withAuth'
import { getPlayerAccess, generateAuthorizationError } from '@/lib/classroom'
import { getUserId } from '@/lib/viewer'

export interface PlayerRecordingItem {
  id: string
  sessionId: string
  status: string
  durationMs: number | null
  frameCount: number | null
  fileSize: number | null
  startedAt: string
  endedAt: string | null
  expiresAt: string
  videoUrl: string | null
}

export interface PlayerRecordingsResponse {
  recordings: PlayerRecordingItem[]
  totalCount: number
}

/**
 * GET - List all recordings for a player
 */
export const GET = withAuth(async (request, { params }) => {
  try {
    const { playerId } = (await params) as { playerId: string }

    if (!playerId) {
      return NextResponse.json({ error: 'Player ID required' }, { status: 400 })
    }

    // Authorization check
    const userId = await getUserId()
    const access = await getPlayerAccess(userId, playerId)
    if (access.accessLevel === 'none') {
      const authError = generateAuthorizationError(access, 'view', {
        actionDescription: 'view recordings for this student',
      })
      return NextResponse.json(authError, { status: 403 })
    }

    // Parse pagination from query params
    const url = new URL(request.url)
    const limit = Math.min(parseInt(url.searchParams.get('limit') || '20', 10), 100)
    const offset = parseInt(url.searchParams.get('offset') || '0', 10)

    // Get recordings for this player, sorted by start time
    const recordings = await db.query.visionRecordings.findMany({
      where: eq(visionRecordings.playerId, playerId),
      orderBy: [desc(visionRecordings.startedAt)],
      limit,
      offset,
    })

    // Get total count
    const allRecordings = await db.query.visionRecordings.findMany({
      where: eq(visionRecordings.playerId, playerId),
      columns: { id: true },
    })
    const totalCount = allRecordings.length

    // Transform to response format
    const result: PlayerRecordingItem[] = recordings.map((recording) => ({
      id: recording.id,
      sessionId: recording.sessionId,
      status: recording.status,
      durationMs: recording.durationMs,
      frameCount: recording.frameCount,
      fileSize: recording.fileSize,
      startedAt: recording.startedAt.toISOString(),
      endedAt: recording.endedAt?.toISOString() ?? null,
      expiresAt: recording.expiresAt.toISOString(),
      videoUrl:
        recording.status === 'ready'
          ? `/api/curriculum/${playerId}/sessions/${recording.sessionId}/recording/video`
          : null,
    }))

    const response: PlayerRecordingsResponse = {
      recordings: result,
      totalCount,
    }

    return NextResponse.json(response)
  } catch (error) {
    console.error('Error fetching player recordings:', error)
    return NextResponse.json({ error: 'Failed to fetch recordings' }, { status: 500 })
  }
})